November 8, 2019
Also: If you need to see all the data points at the same time, don’t use animation
Thomas Lin Pedersen’s example from https://gganimate.com - (@thomasp85)
What information do you want the audience to take away?
Movement and speed lead the viewer to interpretation of the data - this can be good or bad
Each static frame should be informative alone as well
(Be open to the idea that you might not need animation to be most effective)
library(ggplot2) # make plots library(gganimate) # animate the plots library(gifski) # render gifs
Optional, maybe interesting packages
library(transformr) # additional smooth transformations library(patchwork) # arrange multiple plots in a single layout library(ggforce) # extended options for ggplot functionality
Using data on displaced persons living in South Africa, show patterns and trends in refugee movement over twenty-plus years.
Where do most refugees come from?
Is the number of refugees coming from the most common countries of origin stable?
Data Source: UNHCR via data.world: UNHCR’s populations of concern residing in South Africa
Grab dataset of displaced persons living in South Africa
| X_date_year | X_country_residence | X_country_origin | X_population_type | X_affected |
|---|---|---|---|---|
| 1996 | South Africa | Angola | Refugees (incl. refugee-like situations) | 3876 |
| 1996 | South Africa | Angola | Returned refugees | 308 |
| 1996 | South Africa | Bangladesh | Refugees (incl. refugee-like situations) | 452 |
| 1996 | South Africa | China | Refugees (incl. refugee-like situations) | 469 |
Select the top 10 countries of origin for each year, apply some filters.
| X_date_year | X_country_residence | X_country_origin | X_population_type | X_affected | rank |
|---|---|---|---|---|---|
| 1996 | South Africa | Angola | Refugees (incl. refugee-like situations) | 3876 | 1 |
| 1996 | South Africa | Bangladesh | Refugees (incl. refugee-like situations) | 452 | 9 |
| 1996 | South Africa | China | Refugees (incl. refugee-like situations) | 469 | 8 |
| 1996 | South Africa | Dem. Rep. of the Congo | Refugees (incl. refugee-like situations) | 2505 | 3 |
Group by country, bar height is affected persons
baseplot1 <- ggplot(
plotDT,
aes(X_date_year,
group = X_country_origin,
fill = as.factor(X_country_origin),
color = as.factor(X_country_origin))) +
theme_bw() +
theme(legend.position = "bottom") +
geom_bar(aes(y = X_affected), stat = "identity", position = "dodge")
Ew. This is not effective.
No.
Nooooo.
Let’s make this!
From this…
baseplot1 <- ggplot(plotDT,
aes(X_date_year,
group = X_country_origin,
fill = as.factor(X_country_origin),
color = as.factor(X_country_origin))) +
theme_bw() +
theme(legend.position = "bottom") +
geom_bar(aes(y = X_affected), stat = "identity", position = "dodge")
To this!
baseplot3 <- ggplot(plotDT,
aes(x=rank,
group = X_country_origin,
fill = as.factor(X_country_origin),
color = as.factor(X_country_origin)))+
theme_bw()+
theme(legend.position = "none", # Give the plot enough space to have labels
axis.ticks.y = element_blank(),
axis.text.y = element_blank(),
axis.title.y = element_blank(),
plot.margin = margin(1,1,1,5, "cm"))+
geom_text(aes(y = 0, label = paste(X_country_origin, " ")), vjust = 0.2, hjust = 1) +
coord_flip(clip = "off", expand = FALSE, ylim = c(0, 50000)) + # Flip the plot
geom_bar(aes(y = X_affected), stat = "identity", position = "identity")+
scale_y_continuous(labels = scales::comma) +
scale_x_reverse()
Now we have the different “frames” all layered on top of each other.
Literally add one more line of code to your ggplot object.
animp <- baseplot3 + transition_states(X_date_year)
It’s nice, but we can do better
Solving Problem 1 and 2: Added a descriptive title/label that indicate the year of the frame, label bars
animp <- baseplot3 +
geom_text(aes(y = X_affected,
label = as.character(X_affected)),
color = "black", vjust = 0.2, hjust = -.5)+
labs(title = "Refugees Residing in South Africa by Origin, {closest_state}"
, y="Affected Persons")+
transition_states(X_date_year)
Solving Problem 3: how do we want the animation elements to move?
Choose exit and enter styles: grow and shrink?
animp <- baseplot3 +
geom_text(aes(y = X_affected,
label = as.character(X_affected)),
color = "black", vjust = 0.2, hjust = -.5)+
labs(title = "Refugees Residing in South Africa by Origin, {closest_state}"
, y="Affected Persons")+
transition_states(X_date_year)+
enter_grow() +
exit_shrink()
It’s interesting, but probably not serving the project objectives
Choose easing:
Here we see quartic-in-out
animp <- baseplot3 +
geom_text(aes(y = X_affected,
label = as.character(X_affected)),
color = "black", vjust = 0.2, hjust = -.5)+
labs(title = "Refugees Residing in South Africa by Origin, {closest_state}"
, y="Affected Persons")+
transition_states(X_date_year)+
ease_aes('quartic-in-out')
Easing back-in-out: Feels a little cartoony- interesting, but again perhaps not what we need
In addition to entry, exit, and transition easing:
animp <- baseplot3 +
geom_text(aes(y = X_affected,
label = as.character(X_affected)),
color = "black", vjust = 0.2, hjust = -.5)+
labs(title = "Refugees Residing in South Africa by Origin, {closest_state}"
, y="Affected Persons")+
transition_states(X_date_year,transition_length = 5,
state_length = c(rep(.25, 21), 20), wrap = FALSE)+
ease_aes('linear')+
enter_fade() +
exit_fade()
Slower pace feels smoother, and doesn’t insinuate that the last frame and the first flow into each other
transition_states(
states = X_date_year,
transition_length = 5,
state_length = c(rep(.25, 21), 20),
wrap = FALSE)+
States: Assign the state unit - here we use the year.
Transition length
State length
Wrap: determine whether to apply transition smoothing between the end and restarting
Experiment with these settings to get the look you want!
animate(animp, fps = 10, width = 800, height = 450)
anim_save(filename = "final_race_plot2.gif")
Prioritize the transmission of information effectively
Make your plot serve the audience, don’t be fancy if it’s not helpful
Think carefully about transitions and speed
Get feedback and test your animation on naive viewers
https://ggplot2.tidyverse.org/
https://ggforce.data-imaginist.com/
https://stackoverflow.com/questions/53162821/animated-sorted-bar-chart-with-bars-overtaking-each-other/53163549 (Hat tip to Jon Spring for this awesome starting point for this kind of thing!)
Similar project in D3: https://observablehq.com/@johnburnmurdoch/bar-chart-race-the-most-populous-cities-in-the-world